#!/bin/bash
#XXX: work in progress

#inspired from the first topmost answer, from here: https://stackoverflow.com/questions/14103806/bash-test-if-a-directory-is-writable-by-a-given-uid

set +e
set +u
set +x

#set to 1 if you want to bail(exit script) in cases where for example the caller used wrong number of parameters when calling a function:
FUNCTIONS_EXIT_ON_BUGS=1
#anything other than 1 means the function just returns with some specific exit code representing the error.

#set to 1 if you want functions to also echo(to std out) the return(or exit) value:
FUNCTIONS_ALSO_ECHO_EXITCODES=1

if test "$#" -ne "2"; then
  echo "Usage: $0 user path"
  if test -n "$DEBUG" ; then
    echo "you tried: '$0 $@'" >&2
    if test "$#" -gt "2"; then
      echo "did you mean to use quotes(like ' or \") around the path?" >&2
    fi
  fi
  exit 2
fi

invariants_cnt()
{
  #call it like this: invariants_cnt $# 1 "$@"
  #where 1 is the only one you need to change in that call, and it's the number of expected params
  #ie. your function expects exactly 2 params then you use this exact statement: invariants_cnt $# 2 "$@"

  local IFS=',' #delimiter used when expanding params via $*
#  echo "${FUNCNAME[0]} params($#): '$*'" >&2
  local passedcnt=$1
  local expectedcnt=$2
  #FIXME: are numbers?
  shift 2
  if test "$passedcnt" -ne "$expectedcnt"; then
    echo "Passed incorrect number of params(${passedcnt}/${expectedcnt}) to ${FUNCNAME[1]}(): '$*'" >&2
    #TODO: print entire stack trace ie. stack dump, requires copying code from funx.bash OR sourcing it
    exit 128 #bug in code
  fi
}

returning()
{
  invariants_cnt $# 1 "$@"
#  echo "$*"
  local ec="$1"
  #FIXME: is a number?
  if test "$FUNCTIONS_ALSO_ECHO_EXITCODES" -eq "1"; then
    echo $ec #trim
  fi
  return $ec #trim
}
returning 10
returning 10 11 12 " a b c " " d  "
exit

rexit()
{
  local ec="$1"
  local cnt=1
  if test "$#" -ne "$cnt"; then
    echo "Passed incorrect number of params(${#}/${cnt}) to rexit(): $@" >&2
    exit 128 #bug in code
  fi
  returning $ec  #trim #allow trimming of whitespace, by not double quoting
  if test "$FUNCTIONS_EXIT_ON_BUGS" -eq "1" ; then
    exit $ec #trim
  fi
  #else
  return $ec #trim
}

is_user_in_group()
{
  #XXX: do not trim whitespace from params! ever. Saying this because I considered using a trim function; and not to imply they are auto trimmed (well unless you pass them undoublequoted)
  local usr="$1"
  local grp="$2"
#  echo ".${1}.${usr}."
#  exit
  local cnt=2 # expected number of params
  #if [ -z "$usr" ] || [ -z "$grp" ]; then
  if test "$#" -ne "$cnt"; then
    echo "you didn't pass correct number of parameters($#/${cnt}): '$@'" >&2
    local ec=10
    rexit $ec  #may or may not exit script
    return $ec #if ^ didn't exit, returning value instead; need bash macros or a way to use only 1 statement to return but also run the hooks above: that rexit thing; maybe using a trap RETURN
  fi
  # Is user in that group?
  theirgroups="$(groups $usr)"
  if test "$?" -ne "0" ; then
    echo "failed to run the command groups, likely the user '$usr' doesn't exist." >&2
    local ec=11
    if test "$FUNCTIONS_ALSO_ECHO_RETURNS" -eq "1" ; then
      echo $ec
    fi
    return $ec
  fi
  for g in ${theirgroups}; do
    if test "$grp" = "$g"; then
      if test -n "$DEBUG" ; then
        echo "found group: $g" >&2
      fi
      echo 1 #FIXME: reverse, 0 is good, 1 is bad
      return 1
    fi
  done
  echo 0
  return 0
}

does_user_have_access_to()
{
  local USER="$1"
  local DIR="$2"
  #TODO: handle invariants

  # Use -L to get information about the target of a symlink,
  # not the link itself, as pointed out in the comments
  local ACCESS=nonexistent
  local INFO=( $(stat -c "%a %G %U" -L -- "$DIR" 2>/dev/null ) )
  if test "$?" -eq "0" ; then
    ACCESS=no
    local PERM="0${INFO[0]}"
    local GROUP="${INFO[1]}"
    local OWNER="${INFO[2]}"
    if test -z "$PERM" -o -z "$GROUP" -o -z "$OWNER"; then
      echo "stat did not output things as expected, got: '${INFO[@]}'" >&2
      exit 3
    fi

    if test -n "$DEBUG"; then
      echo "stat: '${INFO[@]}'" >&2
#      echo "$(( $PERM & 0002 ))" >&2
    fi

    local is_owner=0
    if test "$USER" = "$OWNER" ; then
      is_owner=1
    fi

    local same_group=0

#way1:
    if test "$(is_user_in_group "$USER" "$GROUP")" -eq "1" ; then

#way2:    if ! is_user_in_group "$USER" "$GROUP" 1>/dev/null; then
#this doesn't differentiate between the cases when exit code is ==1 or ==10

#way3:
#    is_user_in_group "$USER" "$GROUP" 1>/dev/null
#    if test "$?" -eq "1" ; then

      same_group=1
    fi

    if test "$is_owner" -eq "1" ; then
      if test "$(( $PERM & 0200 ))" -ne "0" ; then
        ACCESS=owner
      fi
    elif test "$same_group" -eq "1"; then
      if test "$(( $PERM & 0020 ))" -ne "0" ; then
        ACCESS=group
      fi
    elif test "$(( $PERM & 0002 ))" -ne "0" ; then
      #if you're not owner and none of your groups are specified, then 'other' applies to you
      ACCESS=other
    fi
  fi

  echo "$ACCESS"
  return
}


theUSER="$1"
theDIRorFile="$2"

is_user_in_group "$theUSER"

acc="$( does_user_have_access_to "$theUSER" "$theDIRorFile" )"

echo "result: '$acc'"
is_user_in_group
is_user_in_group abc
is_user_in_group abc def
is_user_in_group abc def ghi
#echo $?

is_user_in_group "emacs" "users" 1>/dev/null
if test "$?" -eq "1" ; then
  echo "YES!"
fi
if ! is_user_in_group "emacs" "users" 1>/dev/null; then
  #don't use this way!
  #this is the broken way, ie. exit code ==1 or ==10  is no difference, as long as it's != 0 you're here
  echo "YES2!"
fi


